﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Peers;
using System.Windows.Media;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Real.Silverlight.Controls
{
    public delegate void DataGridHeaderClickedEventHandler(object sender, DataGridHeaderClickedEventArgs e);

    /*REMARKS*/

    /*You must set the RowHeight property in XAML. If the RowHeight property is not set, RowHeight has the value NaN.
     * if the value is NaN, the control can't calculate the proper position of the scrolbar!*/

    /*You can set the associated SynchronousScrollDataGrid in XAML with the following statement:
     * 	<scroll:SynchronousScrollDataGrid  x:Name="firstDataGridName"
     *								       SynchronousScrollingDataGrid="{Binding ElementName=secondDataGridName}"
     *								       RowHeight="25"/>
     */

    /*If you want it two way you must add the same code to your 'secondDataGrid'
     * 	<scroll:SynchronousScrollDataGrid  x:Name="secondDataGridName"
     *								       SynchronousScrollingDataGrid="{Binding ElementName=firstDataGridName}"
     *								       RowHeight="25"/>
     *								       
     */


    public class SynchronousScrollDataGrid : DataGrid
    {

        #region events
        public event DataGridHeaderClickedEventHandler DataGridHeaderClicked;

        protected void OnDataGridHeaderClickend(DataGridHeaderClickedEventArgs e)
        {
            if (DataGridHeaderClicked != null) DataGridHeaderClicked(this, e);
        }
        #endregion

        #region fields
        /// <summary>
        /// The vertical scrollbar.
        /// </summary>
        private ScrollBar verticalScrollBar;

        #endregion

        #region properties
        //dp prop for binding datagrid wich will be synchronous scrolling with this datagrid
        public SynchronousScrollDataGrid SynchronousScrollingDataGrid
        {
            get { return (SynchronousScrollDataGrid)GetValue(SynchronousScrollingDataGridProperty); }
            set
            {
                SetValue(SynchronousScrollingDataGridProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for SynchronousScrollingDataGrid.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SynchronousScrollingDataGridProperty =
            DependencyProperty.Register("SynchronousScrollingDataGrid", typeof(SynchronousScrollDataGrid), typeof(SynchronousScrollDataGrid), null);

        #endregion

        /// <summary>
        /// Load the scrollbars after the template gets loaded.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.LoadScrollBars();

        }

        /// <summary>
        /// Get both scrollbars.
        /// </summary>
        private void LoadScrollBars()
        {
            verticalScrollBar = this.GetTemplateChild("VerticalScrollbar") as ScrollBar;
            if (verticalScrollBar != null)
                verticalScrollBar.Scroll += new ScrollEventHandler(OnVerticalScroll);
        }

        /// <summary>
        /// Overrides the OnMouseWheel for scrolling with the mousewheel.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseWheel(System.Windows.Input.MouseWheelEventArgs e)
        {
            //down => Delta negative
            if (e.Delta < 0)
            {
                OnVerticalScroll(this, new ScrollEventArgs(ScrollEventType.SmallIncrement, this.GetScrollPosition(ScrollMode.Vertical) + this.RowHeight));
            }

            //up => Delta positive
            if (e.Delta > 0)
            {
                OnVerticalScroll(this, new ScrollEventArgs(ScrollEventType.SmallDecrement, this.GetScrollPosition(ScrollMode.Vertical) - this.RowHeight));
            }

            base.OnMouseWheel(e);
        }

        /// <summary>
        /// Overrides the OnMouseLeftButtonDown for fire an event if the user clicked on a DataGridColumnHeader
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            Point pt = e.GetPosition(null);
            var lst = VisualTreeHelper.FindElementsInHostCoordinates(pt, this);
            var lstHeader = new List<DataGridColumnHeader>();
            foreach (var element in lst)
            {
                if (element.GetType().Equals(typeof(DataGridColumnHeader)))
                    lstHeader.Add((DataGridColumnHeader)element);
            }

            if (lstHeader.Count == 1)
            {
                foreach (var item in this.Columns)
                {
                    if (item.Header.Equals(lstHeader[0].Content))
                        OnDataGridHeaderClickend(new DataGridHeaderClickedEventArgs(lstHeader[0], GetBindingName(item)));
                }
            }

            base.OnMouseLeftButtonDown(e);
        }

        /// <summary>
        /// Gets the the name of the binded property of a specific column
        /// </summary>
        ///<returns type="string"></returns>
        private string GetBindingName(DataGridColumn column)
        {
            DataGridBoundColumn bc = column as DataGridBoundColumn;

            if (bc != null)
                return bc.Binding.Path.Path;
            return "";
        }

        /// <summary>
        /// Notify that we are scrolling vertically.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnVerticalScroll(object sender, ScrollEventArgs e)
        {
            if (e.ScrollEventType != ScrollEventType.EndScroll)
            {

                if (e.ScrollEventType == ScrollEventType.SmallIncrement)
                {
                    UpdateScrollPosition(e.NewValue + this.RowHeight);
                }

                else if (e.ScrollEventType == ScrollEventType.SmallDecrement)
                {
                    UpdateScrollPosition(e.NewValue - this.RowHeight);
                }

                else
                    UpdateScrollPosition(e.NewValue);

            }
        }

        /// <summary>
        /// Updates the scoll position on the other datagrid.
        /// </summary>
        /// <param name="newValue"></param>
        private void UpdateScrollPosition(double newValue)
        {
            SynchronousScrollDataGrid otherDg = GetValue(SynchronousScrollingDataGridProperty) as SynchronousScrollDataGrid;
            if (otherDg != null)
                otherDg.Scroll(ScrollMode.Vertical, newValue);
        }
    }

    public static class SynchronousScrollingDataGridScrollExtensions
    {
        /// <summary>
        /// Scroll to a percentage of the scrollbar (50% = half).
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="percent"></param>
        public static void ScrollToPercent(this SynchronousScrollDataGrid grid, ScrollMode mode, double percent)
        {
            // Fix the percentage.
            if (!Double.IsNaN(percent))
            {
                if (percent < 0)
                    percent = 0;
                else if (percent > 100)
                    percent = 100;

                // Get the scroll provider.
                var scrollProvider = GetScrollProvider(grid);

                // Scroll.
                if (scrollProvider != null)
                {
                    switch (mode)
                    {
                        case ScrollMode.Vertical:
                            scrollProvider.SetScrollPercent(System.Windows.Automation.ScrollPatternIdentifiers.NoScroll, percent);
                            break;
                        case ScrollMode.Horizontal:
                            scrollProvider.SetScrollPercent(percent, System.Windows.Automation.ScrollPatternIdentifiers.NoScroll);
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Get the current position of the scrollbar.
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>
        public static double GetScrollPosition(this SynchronousScrollDataGrid grid, ScrollMode mode)
        {
            var scrollBar = grid.GetScrollbar(mode);
            return scrollBar.Value;
        }

        /// <summary>
        /// Scroll to a position of the scrollbar.
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="position"></param>
        public static void Scroll(this SynchronousScrollDataGrid grid, ScrollMode mode, double position)
        {
            // Get the scrollbar and convert the position to percent.
            var scrollBar = grid.GetScrollbar(mode);
            double positionPct = ((position / scrollBar.Maximum) * 100);

            // Scroll to a specfic percentage of the scrollbar.
            grid.ScrollToPercent(mode, positionPct);
        }

        /// <summary>
        /// Get a scroll provider for the grid.
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private static IScrollProvider GetScrollProvider(SynchronousScrollDataGrid grid)
        {
            var p = FrameworkElementAutomationPeer.FromElement(grid) ?? FrameworkElementAutomationPeer.CreatePeerForElement(grid);
            return p.GetPattern(PatternInterface.Scroll) as IScrollProvider;
        }

        /// <summary>
        /// Get one of the grid's scrollbars.
        /// </summary>
        /// <param name="grid"></param>
        /// <param name="mode"></param>
        /// <returns></returns>
        public static ScrollBar GetScrollbar(this SynchronousScrollDataGrid grid, ScrollMode mode)
        {
            if (mode == ScrollMode.Vertical)
                return grid.GetScrollbar("VerticalScrollbar");
            else
                return grid.GetScrollbar("HorizontalScrollbar");
        }

        /// <summary>
        /// Find the scrollbar for our datagrid.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dep"></param>
        /// <returns></returns>
        private static ScrollBar GetScrollbar(this DependencyObject dep, string name)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dep); i++)
            {
                var child = VisualTreeHelper.GetChild(dep, i);
                if (child != null && child is ScrollBar && ((ScrollBar)child).Name == name)
                    return child as ScrollBar;
                else
                {
                    ScrollBar sub = GetScrollbar(child, name);

                    if (sub != null)
                        return sub;
                }
            }
            return null;
        }
    }

    public enum ScrollMode
    {
        Vertical,
        Horizontal
    }

    public class DataGridHeaderClickedEventArgs : EventArgs
    {
        public DataGridColumnHeader ClickedDataGridColumn { get; set; }
        public string BindingPropertyName { get; set; }

        public DataGridHeaderClickedEventArgs(DataGridColumnHeader header, string bindingPropertyName)
        {
            ClickedDataGridColumn = header;
            BindingPropertyName = bindingPropertyName;
        }
    }
}